#pragma once

#include <iostream>
#include <string>
#include <vector>
#include <mutex>
#include <assert.h>
#include <fstream>
#include <jsoncpp/json/json.h>
#include <unistd.h>
#include <algorithm>

#include "../Comm/Log.hpp"
#include "../Comm/Tool.hpp"
#include "../Comm/httplib.h"
// #include "Model_File.hpp"
#include "Model_MySQL.hpp"
#include "View.hpp"

namespace Control_NS
{
    using namespace Log_NS;
    using namespace Tool_NS;
    using namespace Model_NS;
    using namespace View_NS;

    const std::string machine_conf_path = "./Conf/Service_Machine.conf";

    // 服务机器的信息
    class Machine
    {
    public:
        Machine()
            : _ip(""), _port(0), _load(0), _mtx{nullptr}
        {
        }
        ~Machine()
        {
        }

    public:
        // 提高负载
        void IncLoad()
        {
            if (_mtx) _mtx->lock();
            _load++;
            if (_mtx) _mtx->unlock();
        }

        // 降低负载
        void DecLoad()
        {
            if (_mtx) _mtx->lock();
            _load--;
            if (_mtx) _mtx->unlock();
        }

        // 获取负载
        uint64_t GetLoad()
        {
            uint64_t load;
            if (_mtx) _mtx->lock();
            load = _load;
            if (_mtx) _mtx->unlock();

            return load;
        }

        // 重置负载
        void ResetLoad()
        {
            if (_mtx) _mtx->lock();
            _load = 0;
            if (_mtx) _mtx->unlock();
        }

    public:
        std::string _ip;  // 服务的ip
        uint16_t _port;   // 服务的端口
        uint64_t _load;   // 服务的负载
        std::mutex *_mtx; // 锁，这里使用指针，原因时C++中的mutex禁止拷贝
    };

    // 负载均衡
    class LoadBalance
    {
    public:
        LoadBalance()
        {
            assert(LoadConf(machine_conf_path));
        };
        ~LoadBalance() {};

    public:
        // 加载配置文件
        bool LoadConf(const std::string &machine_path)
        {
            std::ifstream in(machine_path);
            if (!in.is_open())
            {
                LOG(Fatal) << "加载编译运行机器配置文件失败" << std::endl;
                return false;
            }

            std::string line;
            while (std::getline(in, line))
            {
                std::vector<std::string> v_sub;
                StringTool::SplitString(line, &v_sub, ":");

                if (v_sub.size() != 2)
                {
                    LOG(Warning) << "分割" << line << "失败";
                    continue;
                }

                Machine machine;
                machine._ip = v_sub[0];
                machine._port = std::stoi(v_sub[1]);
                machine._load = 0;
                machine._mtx = new std::mutex();

                _online.push_back(_machines.size());
                _machines.push_back(machine);
            }

            LOG(Info) << "加载编译运行机器配置文件成功" << std::endl;

            in.close();
            return true;
        }

        // 智能选择，选择负载低的机器
        bool SmartChoice(int *id, Machine **machine)
        {
            _mtx.lock();

            int online_num = _online.size();
            if (online_num == 0)
            {
                LOG(Fatal) << "目前没有机器提供编译运行服务" << std::endl;
                _mtx.unlock();
                return false;
            }

            *id = _online[0];
            *machine = &_machines[_online[0]];

            uint64_t min_load = _machines[_online[0]].GetLoad();
            for (int i = 1; i < online_num; i++)
            {
                int cur_load = _machines[_online[i]].GetLoad();
                if (min_load > cur_load)
                {
                    min_load = cur_load;
                    *id = i;
                    *machine = &_machines[_online[i]];
                }
            }

            _mtx.unlock();

            return true;
        }

        // 将所有的离线机器重新上线
        void OnlineMachine()
        {
            _mtx.lock();
            _online.insert(_online.end(),_offline.begin(),_offline.end());
            _offline.clear();
            LOG(Info) << "所有离线的机器已经重新上线了" << std::endl;
            _mtx.unlock();
        }

        // 离线指定的机器
        void OfflineMachine(int id)
        {
            _mtx.lock();
            for(auto iter = _online.begin();iter != _online.end();iter++)
            {
                if(*iter == id)
                {
                    _machines[id].ResetLoad();
                    _online.erase(iter);
                    _offline.push_back(id);
                    break;
                }
            }
            _mtx.unlock();
        }

        // 显示机器列表
        void ShowMachines()
        {
            std::cout << "当前在线机器列表" ;
            for(auto& id : _online)
            {
                std::cout << id << " ";
            }
            std::cout << std::endl;

            std::cout << "当前离线机器列表" ;
            for(auto& id : _offline)
            {
                std::cout << id << " ";
            }
            std::cout << std::endl;
        }

    private:
        std::vector<Machine> _machines; // 所有提供服务的主机
        // 每台主机在_machines中的下标就作为主机的编号
        std::vector<int> _online;  // 将主机编号放在_online中就代表当前主机在线
        std::vector<int> _offline; // 将主机编号放在_offline中就代表当前主机离线

        std::mutex _mtx;
    };

    class Control
    {
    public:
        Control() {};
        ~Control() {};

    public:
        // 获取所有题目
        bool AllQuestions(std::string *html)
        {
            bool flag = true;
            // 获取题库信息
            std::vector<Question> all;
            if (_model.GetAllQuestions(&all))
            {
                // 将所有题目信息排序后转化为网页
                sort(all.begin(),all.end(),[](const Question& q1,const Question& q2){
                    return std::stoi(q1._number) < std::stoi(q2._number);
                });
                _view.AllExpandHtml(all, html);
            }
            else
            {
                LOG(Fatal) << "获取题库失败,获取题目列表失败" << std::endl;
                flag = false;
            }
            return flag;
        }

        // 获取指定题目
        bool OneQuestion(const std::string &number, std::string *html)
        {
            bool flag = true;
            Question q;
            if (_model.GetOneQuestion(number, &q))
            {
                // 将指定题目信息转化为网页
                _view.OneExpandHtml(q, html);
            }
            else
            {
                LOG(Fatal) << "获取题目" << number << "失败" << std::endl;
                flag = false;
            }
            return false;
        }

        // 判断提交代码是否正确
        void Judge(const std::string &number, const std::string &in_json, std::string *out_json)
        {
            // 根据题号，获取题目信息
            Question q;
            _model.GetOneQuestion(number, &q);

            // 反序列化获取用户编写的代码和input
            Json::Value in_value;
            Json::Reader reader;
            reader.parse(in_json, in_value);
            std::string user_code = in_value["code"].asString();

            // 将用户提交的代码与测试样例结合，再反序列化
            Json::Value compile_run_value;
            compile_run_value["code"] = user_code + "\n" + q._test;
            compile_run_value["input"] = in_value["input"].asString();
            compile_run_value["cpu_limit"] = q._cpu_limit;
            compile_run_value["mem_limit"] = q._mem_limit;

            Json::FastWriter writer;
            std::string compile_run_str = writer.write(compile_run_value);

            // 负载均衡选择服务主机
            // 一直选择，直到某机器完成服务，或所有机器全部离线
            while (true)
            {
                int id = 0;
                Machine *m = nullptr;

                if (!_load_balance.SmartChoice(&id, &m))
                {
                    break;
                }

                // 发起http请求，得到结果
                httplib::Client client(m->_ip, m->_port);

                m->IncLoad(); // 提升负载
                LOG(Info) << "选择主机成功,主机id :" << id << " , 主机详细信息: " \
                    << m->_ip << ":" << m->_port << " , 当前主机的负载为: " << m->GetLoad() << std::endl;
                // Post的返回值为Result,Result包含两个成员变量:std::unique_ptr<Response> res_ , Error err_;
                // Post没有响应就是请求失败,有响应不一定是请求成功,必须只有是Response中的状态码为200时，响应才是完全成功的
                if (auto res = client.Post("/Compile_Run", compile_run_str, "application/json;charset=utf-8"))
                {   
                    if (res->status == 200)
                    {
                        // 将结果赋值给out_json返回
                        *out_json = res->body;
                        m->DecLoad(); // 请求成功,降低负载
                        LOG(Info) << "请求编译运行服务成功" <<std::endl;
                        break;
                    }
                    m->DecLoad(); // 请求失败,降低负载
                }
                else
                {
                    LOG(Error) << "主机id :" << id << " , 主机详细信息: " << m->_ip << ":" << m->_port << ",可能已经离线" << std::endl;
                    _load_balance.OfflineMachine(id);
                    _load_balance.ShowMachines();
                }
                sleep(1);
            }
        }

        // 恢复所有主机
        void RecoverMachine()
        {
            _load_balance.OnlineMachine();
        }

    private:
        Model _model;
        View _view;
        LoadBalance _load_balance;
    };
}